package com.theeviljames.complexANN;

import com.theeviljames.exceptions.ComplexANNException;
import com.theeviljames.pure.Complex;
import com.theeviljames.pure.ComplexMatrixOps;
import com.theeviljames.pure.IComplexMatrixOps;

/**
 * This class represents a single element in a flat Complex ANN. 
 * For example in a 2x2 ANN there would be one of these with 2 input
 * nodes and 2 output nodes. A 2x2x2 would have two of these etc. The basic
 * component is simply a weight matrix and outputs are obtained by:
 * Weights x Inputs = Outputs
 * 
 * @author James McTaggart
 *
 */
public class ComplexANNLayer {

	private static IComplexMatrixOps c = ComplexMatrixOps.getComplexMatrixOps();	
	private Complex[][] weights;
	private final double limit = 1.0;
	private final boolean signed = true;
	private boolean linear;
	private Complex learnRate;
	private Complex[][] inputs;
	
	public ComplexANNLayer(int inputs, int outputs, boolean linear, Complex learnRate) {
		weights = new Complex[inputs][outputs];
		for(int i = 0; i < inputs; i++){
			for(int j = 0; j < outputs; j++){
				weights[i][j] = Complex.getRandom(limit, signed);
			}
		}
		this.linear = linear;
		this.learnRate = learnRate;
	}

	public Complex[][] getOutputs(Complex[][] inputs) throws ComplexANNException{
		if(inputs.length!=weights.length)throw new ComplexANNException("The inputs must be a 2D column matrix with 1 entry in each row. Furthermore the number of rows must be equal to the number of input nodes");
		this.inputs = inputs;
		if(linear){
			return c.times(weights, inputs);
		}
		return c.sigmoid(c.times(weights, inputs));
	}
	
	public Complex[][] backprop(Complex[][] deltaError) throws ComplexANNException{
		System.out.println("Deltas");
		c.print(deltaError);
		Complex[][] dInputs = c.deltaSigmoid(inputs);
		Complex[][] weightChange = c.scalarTimes(c.times(inputs,c.transpose(deltaError)),learnRate);
		System.out.println("WeightChange");
		c.print(weightChange);
		Complex[][] newDeltaError = c.times(dInputs, c.times(weights, deltaError));
		weights = c.minus(weights,weightChange);
		return newDeltaError;
		
	}
	
	public void print(){
		System.out.println("Complex ANN Layer comprising of " + weights.length + " inputs and " + weights[0].length + " outputs\n");
		c.print(weights);
	}
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try{
			ComplexANNLayer ann = new ComplexANNLayer(2,2,false, new Complex(0.05,0.05));
			ann.print();
			Complex[][] inputs = new Complex[][]{{new Complex(1,2)},{new Complex(2,1)}};
			System.out.println("Inputs\n");
			c.print(inputs);
			Complex[][] outputs = ann.getOutputs(inputs);
			System.out.println("Outputs\n");
			c.print(outputs);
			Complex[][] delta = new Complex[][]{{new Complex(0.34,0.8)},{new Complex(0.34,0.8)}};
			Complex[][] newDelta = ann.backprop(delta);
			c.print(newDelta);
			ann.print();
			
		}
		catch(Exception e){
			e.printStackTrace();
		}
	}

}
